/**
 * @author Da Mu
 * @version 1.1.0
 * @mail 317782199@qq.com
 */
import { Injectable, HttpException, HttpStatus, NotFoundException, Inject } from '@nestjs/common';
import { TemplateApiFall, TemplateApiGame } from '../TemplateApi.dto';
import { TemplateApiRedisService } from '../TemplateApiPlugService/TemplateApiRedis.service';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger as WinstonLogger } from 'winston';
import * as jsonfile from 'jsonfile';
import { join } from 'path';
import * as crypto from 'crypto';
import * as fs from 'fs';

interface marblesType {
    name: string,
    type: string,
    weight: number
};

interface zombieType {
    name: string,
    type: string,
    quantity: number
};

interface centerType {
    x: number,
    z: number
};

interface coordinateType {
    name: string,
    coordinate: {
        x: number,
        z: number
    }
};

interface zombieDataType {
    hp: number,
    name: string,
    atk: number,
    type: string,
    weight: number
};

interface playerDataType {
    id: string,
    hp: number,
    name: string,
    brainCount: number,
    ghostCount: number
};

class Marbles {
    private readonly _marblesType: Array<marblesType>;
    constructor() {
        this._marblesType = [
            {
                name: '脑子',
                type: 'waste',
                weight: 60
            },
            {
                name: '脑子',
                type: 'ordinary',
                weight: 20
            },
            {
                name: '脑子',
                type: 'rare',
                weight: 10
            },
            {
                name: '脑子',
                type: 'epic',
                weight: 9.5
            },
            {
                name: '脑子',
                type: 'legend',
                weight: .5
            },
        ];

    };

    async count() {

        const totalWeight = this._marblesType.reduce((sum, item) => sum + item.weight, 0);
        const random = crypto.randomInt(0, totalWeight);
        let cumulativeWeight = 0;
        for (const item of this._marblesType) {
            cumulativeWeight += item.weight;
            if (random < cumulativeWeight) {
                return item.type;
            };
        };
        return null;

    };


};

class Zombie {
    protected _zombieDataType: Array<zombieDataType>;
    protected _zombieName: string;
    constructor() {
        this._zombieName = '幽灵';
        this._zombieDataType = [
            {
                hp: 100,
                name: '幽灵',
                atk: 10,
                type: 'waste',
                weight: 90
            },
            {
                hp: 200,
                name: '幽灵',
                atk: 20,
                type: 'legend',
                weight: 10
            },
        ];
    };

    async count() {
        const totalWeight = this._zombieDataType.reduce((sum, item) => sum + item.weight, 0);
        const random = crypto.randomInt(0, totalWeight);
        let cumulativeWeight = 0;
        for (const item of this._zombieDataType) {
            cumulativeWeight += item.weight;
            if (random < cumulativeWeight) {
                return item;
            };
        };
        return null;
    };

};

class Player {
    protected _playerDataType: Array<playerDataType>;
    constructor() {
        this._playerDataType = [
            {
                id: '',
                name: 'Player',
                hp: 100,
                brainCount: 0,
                ghostCount: 0
            }
        ];
    };
};

class RandomWeight extends Marbles {
    private readonly _zombieType: Array<zombieType>;
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: WinstonLogger
    constructor() {
        super();
        this._zombieType = [
            {
                name: '幽灵',
                type: 'waste',
                quantity: 3
            },
            {
                name: '幽灵',
                type: 'legend',
                quantity: 10
            }
        ];
    };

    async who(pn: string, type: string) {
        const data = this._zombieType.find(item => item.type === type);
        return await this.out(pn, data.quantity);
    };

    async out(pn: string, quantity: number) {
        const marblesItems = [];
        for (let i = 0; i < quantity; i++) {
            const item = await this.count();
            if (item) {
                marblesItems.push(item);
            };
        };
        this.logger.info({ uuid: pn, method: '@TemplateApiGame.service -> class.RandomWeight.out', data: marblesItems });

        return marblesItems;
    };

};

class Distribution extends Zombie {
    constructor() {
        super();
    };

    async where(radius: number, center: centerType) {
        let zombieItems: Array<zombieDataType> = [];
        let cordItems: Array<coordinateType> = [];
        const random = crypto.randomInt(3, 10);
        for (let i = 0; i < random; i++) {
            cordItems.push(await this.place(radius, center, this._zombieName));
            zombieItems.push(await this.count());
        };

        return {
            zombie: zombieItems,
            coordinate: cordItems
        };
    };

    async place(radius: number, center: centerType, name: string) {
        const angle = Math.random() * Math.PI * 2;
        /** Randomly generate a radius (0 to spawnRadius) */
        const randomRadius = Math.sqrt(Math.random()) * radius; // Use SQRT to ensure even distribution
        /** Convert polar coordinates to Cartesian coordinates */
        const x = center.x + randomRadius * Math.cos(angle);
        const z = center.z + randomRadius * Math.sin(angle);
        return {
            name: name,
            coordinate: { x, z }
        };
    };

};


@Injectable()
export class TemplateApiGameMarblesService extends RandomWeight {
    constructor(
        private readonly redis: TemplateApiRedisService
    ) {
        super();
    };

    async fall(body: TemplateApiFall): Promise<Array<string>> {
        const json = await this.redis.get('privateSceneGameMonstersGhostData');
        if (json === null) return;
        const findItem = json.zombie.find((zombie: { uuid: string; }) => zombie.uuid === body.uuid);
        const data = await this.who(body.uuid, findItem.type);

        return data;
    };

};

@Injectable()
export class TemplateApiGameDistributionService extends Distribution {
    constructor() {
        super();
    };

    async randomDistributionOfCircularAreas(radius: number = 7, center: centerType = { x: 0, z: 0 }): Promise<any> {
        const data = await this.where(radius, center);

        return JSON.stringify(data);
    };

};

@Injectable()
export class TemplateApiGamePlayerService extends Player {
    private readonly filePath: string = join(process.cwd(), 'static/game', 'save.json');
    constructor(
        private readonly redis: TemplateApiRedisService
    ) {
        super();
    };

    async createPlayer(playerId: string): Promise<string> {
        this._playerDataType.map(item => {
            item.id = playerId
        });
        const beBornJSON = {
            "playerDataType": this._playerDataType[0],
            "coordinate": {
                "x": 5,
                "z": 5
            }
        };
        jsonfile.writeFileSync(this.filePath, beBornJSON);
        await this.redis.set('playerHP', this._playerDataType[0].hp);
        await this.redis.set('playerBrainCount', this._playerDataType[0].brainCount);
        await this.redis.set('ghostCount', this._playerDataType[0].ghostCount);
        return await this.redis.set('playerDataType', this._playerDataType);
    };

    async delPlayerDataType(): Promise<number> {
        return await this.redis.delete(['playerDataType']);
    };

};

@Injectable()
export class TemplateApiGameSaveFileService {
    private readonly filePath: string = join(process.cwd(), 'static/game', 'save.json');
    constructor(
        @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: WinstonLogger,
        private readonly redis: TemplateApiRedisService
    ) { };

    async readSave(body: TemplateApiGame): Promise<JSON | object> {
        try {

            if (body.sw.type === 'read') {
                jsonfile.readFileSync(this.filePath);
                const json = await jsonfile.readFileSync(this.filePath);
                await this.redis.set('playerDataType', json);
                this.logger.info({ event: 'player is read game!', method: '@TemplateApiGameSaveFileService.service -> class.readSave', data: json });
                return {
                    code: HttpStatus.OK,
                    data: json
                };
            };

            if (body.sw.type === 'save') {
                if (body.sw.data) {
                    const playerData = await this.redis.get('playerDataType');
                    const playerBrainCount = await this.redis.get('playerBrainCount');
                    const ghostCount = await this.redis.get('ghostCount');
                    const playerHP = await this.redis.get('playerHP');

                    if (playerData === null || playerBrainCount === null || playerHP === null || ghostCount === null) {
                        return {
                            code: HttpStatus.INTERNAL_SERVER_ERROR,
                            message: null
                        };
                    } else {
                        const saveData = {
                            "playerDataType": {
                                "id": playerData.playerDataType.id,
                                "name": playerData.playerDataType.name,
                                "hp": playerHP,
                                "brainCount": playerBrainCount,
                                "ghostCount": ghostCount
                            },
                            "coordinate": {
                                "x": body.sw.data.coordinate.x,
                                "z": body.sw.data.coordinate.z
                            }
                        };

                        const json = jsonfile.writeFileSync(this.filePath, saveData);
                        this.logger.info({ event: 'player is save game!', method: '@TemplateApiGameSaveFileService.service -> class.readSave', data: json });
                        return {
                            code: HttpStatus.OK,
                            message: '<<<+*+*+GameSaveDone+*+*+>>>'
                        };
                    };
                };
            };

        } catch (error) {
            throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
        };

    };

};

@Injectable()
export class TemplateApiGameSkillListService {
    constructor() { };

    async list(): Promise<JSON | object> {
        try {
            const filePath_Q = join(process.cwd(), 'static/game/images', 'arrow-scope.png');
            const filePath_empty = join(process.cwd(), 'static/game/images', 'resting-vampire.png');
            if (!fs.existsSync(filePath_Q) && !fs.existsSync(filePath_empty)) throw new NotFoundException('file not found');
            const base64StringQ = fs.readFileSync(filePath_Q, { encoding: 'base64' });
            const base64StringEmpty = fs.readFileSync(filePath_empty, { encoding: 'base64' });
            return {
                code: HttpStatus.OK,
                count: 5,
                skillImage: [{ url: 'data:image/png;base64,' + base64StringQ }, { url: 'data:image/png;base64,' + base64StringEmpty }, { url: 'data:image/png;base64,' + base64StringEmpty }, { url: 'data:image/png;base64,' + base64StringEmpty }, { url: 'data:image/png;base64,' + base64StringEmpty }],
                skillCdMask: [{
                    ref: 'cdMask1',
                    timer: ''
                }, {
                    ref: 'cdMask2',
                    timer: ''
                }, {
                    ref: 'cdMask3',
                    timer: ''
                }, {
                    ref: 'cdMask4',
                    timer: ''
                }, {
                    ref: 'cdMask5',
                    timer: ''
                }]
            };
        } catch (error) {
            throw new HttpException(error, HttpStatus.INTERNAL_SERVER_ERROR);
        };

    };

};

@Injectable()
export class TemplateApiScheduledTasksService {
    constructor(
        private readonly redis: TemplateApiRedisService
    ) { };

    async task(data: string): Promise<JSON | object> {
        const status = await this.redis.set('refreshGhostStatus', data);

        return {
            code: HttpStatus.OK,
            msg: status,
        };
    };

    async listenTask(callback: any): Promise<void> {
        const status = await this.redis.get('refreshGhostStatus');
        callback(status);
    };

    async delTask(callback: any): Promise<void> {
        const status = await this.redis.delete(['refreshGhostStatus', 'privateSceneGameMonstersGhostData']);
        callback(status);
    };

};